#include "global.h"
#include "staticfunctions.h"
#include "trisetobject.h"
#include "matrix.h"


void
TrisetObject::gridSize(int &nx, int &ny, int &nz)
{
  nx = m_nX;
  ny = m_nY;
  nz = m_nZ;
}

void TrisetObject::setOpacity(float op) { m_opacity = op; }

TrisetObject::TrisetObject() { clear(); }

TrisetObject::~TrisetObject() { clear(); }

void
TrisetObject::enclosingBox(Vec &boxMin,
			   Vec &boxMax)
{
  boxMin = m_enclosingBox[0];
  boxMax = m_enclosingBox[6];
}

void
TrisetObject::clear()
{
  m_fileName.clear();
  m_centroid = Vec(0,0,0);
  m_nX = m_nY = m_nZ = 0;
  m_color = Vec(0.3f,0.6f,0.8f);
  m_opacity = 0.7f;
  m_specular = 1.0f;
  m_diffuse = 1.0f;
  m_ambient = 0.0f;
  m_pointMode = true;
  m_shadows = false;
  m_pointSize = 5;
  m_pointStep = 1;
  m_vertices.clear();
  m_normals.clear();
  m_triangles.clear();
  m_texValues.clear();
  m_tvertices.clear();
  m_tnormals.clear();
}

bool
TrisetObject::load(QString flnm)
{
  QFile fd(flnm);
  fd.open(QFile::ReadOnly);

  uchar stype = 0;
  fd.read((char*)&stype, sizeof(uchar));
  if (stype != 0)
    {
      QMessageBox::critical(0, "Cannot load triset",
			    "Wrong input format : First byte not equal to 0");
      return false;
    }

  fd.read((char*)&m_nX, sizeof(int));
  fd.read((char*)&m_nY, sizeof(int));
  fd.read((char*)&m_nZ, sizeof(int));


  int nvert, ntri;
  fd.read((char*)&nvert, sizeof(int));
  fd.read((char*)&ntri, sizeof(int));
   

//  QMessageBox::information(0, "", QString("%1 %2 %3\n%4 %5").	\
//			   arg(m_nX).arg(m_nY).arg(m_nZ).\
//			   arg(nvert).\
//			   arg(ntri));


  float *vert = new float[nvert*3];
  fd.read((char*)vert, sizeof(float)*3*nvert);

  float *vnorm = new float[nvert*3];
  fd.read((char*)vnorm, sizeof(float)*3*nvert);

  int *tri = new int[ntri*3];
  fd.read((char*)tri, sizeof(int)*3*ntri);

  fd.close();


  m_vertices.resize(nvert);
  for(int i=0; i<nvert; i++)
    m_vertices[i] = Vec(vert[3*i],
			vert[3*i+1],
			vert[3*i+2]);
  delete [] vert;

  m_normals.resize(nvert);
  for(int i=0; i<nvert; i++)
    m_normals[i] = Vec(vnorm[3*i],
		       vnorm[3*i+1],
		       vnorm[3*i+2]);
  delete [] vnorm;

  m_triangles.resize(3*ntri);
  for(int i=0; i<3*ntri; i++)
    m_triangles[i] = tri[i];
  delete [] tri;


  m_tvertices.resize(nvert);
  m_tnormals.resize(nvert);
  m_texValues.resize(nvert);


  Vec bmin = m_vertices[0];
  Vec bmax = m_vertices[0];
  for(int i=0; i<nvert; i++)
    {
      bmin = StaticFunctions::minVec(bmin, m_vertices[i]);
      bmax = StaticFunctions::maxVec(bmax, m_vertices[i]);
    }
  m_centroid = (bmin + bmax)/2;

  m_enclosingBox[0] = Vec(bmin.x, bmin.y, bmin.z);
  m_enclosingBox[1] = Vec(bmax.x, bmin.y, bmin.z);
  m_enclosingBox[2] = Vec(bmax.x, bmax.y, bmin.z);
  m_enclosingBox[3] = Vec(bmin.x, bmax.y, bmin.z);
  m_enclosingBox[4] = Vec(bmin.x, bmin.y, bmax.z);
  m_enclosingBox[5] = Vec(bmax.x, bmin.y, bmax.z);
  m_enclosingBox[6] = Vec(bmax.x, bmax.y, bmax.z);
  m_enclosingBox[7] = Vec(bmin.x, bmax.y, bmax.z);


  m_pointStep = qMax(1, nvert/50000);

//  QMessageBox::information(0, "", QString("%1 %2 %3\n%4 %5").	\
//			   arg(m_nX).arg(m_nY).arg(m_nZ).\
//			   arg(m_vertices.count()).\
//			   arg(m_triangles.count()));

  m_fileName = flnm;

  return true;
}

bool
TrisetObject::save(QString flnm)
{
  return true;
}


void
TrisetObject::postdraw(QGLViewer *viewer,
		       int x, int y,
		       bool active, int idx)
{
  if (!active)
    return;

  viewer->startScreenCoordinatesSystem();

  glEnable(GL_BLEND);
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blend on top

  QString str = QString("triset %1").arg(idx);
  QFont font = QFont();
  QFontMetrics metric(font);
  int ht = metric.height();
  int wd = metric.width(str);
  //x -= wd/2;
  x += 10;

  glColor4f(0,0,0,0.8f);
  glBegin(GL_QUADS);
  glVertex2f(x, y+2);
  glVertex2f(x+wd+5, y+2);
  glVertex2f(x+wd+5, y-ht);
  glVertex2f(x, y-ht);
  glEnd();
      
  glColor3f(1,1,1);
  viewer->renderText(x+2, y-metric.descent(), str);

  viewer->stopScreenCoordinatesSystem();
}

void
TrisetObject::draw(QGLViewer *viewer,
		   bool active,
		   Vec lightPosition,
		   float pnear, float pfar, Vec step)
{
  if (active)
    {
      Vec lineColor = Vec(0.7f, 0.3f, 0);
      StaticFunctions::drawEnclosingCube(m_tenclosingBox, lineColor);
    }
  
  if (m_opacity == 0)
    return;

  float pos[4];
  float amb[4];
  float diff[4];
  float spec[4];
  float shine = 128*m_specular;

  glEnable(GL_LIGHTING);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_LIGHT0);

  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

  for (int i=0; i<4; i++)
    spec[i] = m_specular;

  for (int i=0; i<4; i++)
    diff[i] = m_diffuse;

  for (int i=0; i<4; i++)
    amb[i] = m_ambient;


  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spec);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &shine);

  // emissive when active
  if (active)
    {
      float emiss[] = { 0.5f, 0, 0, 1 };
      glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emiss);
    }
  else
    {
      float emiss[] = { 0, 0, 0, 1 };
      glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emiss);
    }


  glLightfv(GL_LIGHT0, GL_DIFFUSE, diff);
  glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
  if (spec > 0)
    glLightfv(GL_LIGHT0, GL_SPECULAR, spec);

  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

  pos[0] = lightPosition.x;
  pos[1] = lightPosition.y;
  pos[2] = lightPosition.z;
  pos[3] = 0;
  glLightfv(GL_LIGHT0, GL_POSITION, pos);

  glColor4f(m_color.x*m_opacity,
	    m_color.y*m_opacity,
	    m_color.z*m_opacity,
	    m_opacity);

  drawTriset(pnear, pfar, step);

  { // reset emissivity
    float emiss[] = { 0, 0, 0, 1 };
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emiss);
  }

  glDisable(GL_LIGHTING);
}

void
TrisetObject::predraw(QGLViewer *viewer,
		      double *Xform,
		      Vec pn,
		      bool shadows, int shadowWidth, int shadowHeight)
{
  m_tcentroid = Matrix::xformVec(Xform, m_centroid);

  for(int i=0; i<8; i++)
    m_tenclosingBox[i] = Matrix::xformVec(Xform, m_enclosingBox[i]);

  for(int i=0; i<m_vertices.count(); i++)
    m_tvertices[i] = Matrix::xformVec(Xform, m_vertices[i]);

  for(int i=0; i<m_normals.count(); i++)
    m_tnormals[i] = Matrix::rotateVec(Xform, m_normals[i]);

  if (!shadows || !m_shadows)
    {
      for(int i=0; i<m_vertices.count(); i++)
	m_texValues[i] = Vec(pn*m_tvertices[i], 0, 0);
    }
  else
    {
      for(int i=0; i<m_vertices.count(); i++)
	{
	  Vec scr = viewer->camera()->projectedCoordinatesOf(m_tvertices[i]);
	  float tx = scr.x/(float)shadowWidth;
	  float ty = 1.0f-scr.y/(float)shadowHeight;
	  
	  float d = pn * m_tvertices[i];
	  
	  m_texValues[i] = Vec(d, tx, ty);
	}
    }

}

void
TrisetObject::drawTriset(float pnear, float pfar, Vec step)
{
  if (m_pointMode)
    {
      glEnable(GL_POINT_SPRITE);
      glActiveTexture(GL_TEXTURE0);
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, Global::spriteTexture());
      glTexEnvf( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
      glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
      glEnable(GL_POINT_SMOOTH);
      glPointSize(m_pointSize);
      glBegin(GL_POINTS);
      for(int i=0; i<m_triangles.count()/3; i+=m_pointStep)
	{
	  int v0 = m_triangles[3*i];
	  if ( m_texValues[v0].x >= pnear &&
	       m_texValues[v0].x <= pfar )
	    {
	      glNormal3fv(m_tnormals[v0]);
	      glVertex3fv(m_tvertices[v0]);
	    }
	}
      glEnd();
      glPointSize(1);
      glDisable(GL_POINT_SPRITE);
      glActiveTexture(GL_TEXTURE0);
      glDisable(GL_TEXTURE_2D);
    }
  else
    {
      glBegin(GL_TRIANGLES);
      for(int i=0; i<m_triangles.count()/3; i+=m_pointStep)
	{
	  int v0 = m_triangles[3*i];
	  int v1 = m_triangles[3*i+1];
	  int v2 = m_triangles[3*i+2];
	  
	  if ( ! ((m_texValues[v0].x < pnear &&
		   m_texValues[v1].x < pnear &&
		   m_texValues[v2].x < pnear) ||
		  (m_texValues[v0].x > pfar  &&
		   m_texValues[v1].x > pfar  &&
		   m_texValues[v2].x > pfar)) )
	    {
	      glTexCoord2f(m_texValues[v0].y,
			   m_texValues[v0].z);
	         glNormal3fv(m_tnormals[v0]);
	        glVertex3fv(m_tvertices[v0]+step);
	      
	      glTexCoord2f(m_texValues[v1].y,
			   m_texValues[v1].z);
	         glNormal3fv(m_tnormals[v1]);
	        glVertex3fv(m_tvertices[v1]+step);
	      
	      glTexCoord2f(m_texValues[v2].y,
			   m_texValues[v2].z);
	         glNormal3fv(m_tnormals[v2]);
	        glVertex3fv(m_tvertices[v2]+step);
	    }
	  
	}
      glEnd();
    }
}

QDomElement
TrisetObject::domElement(QDomDocument &doc)
{
  QDomElement de = doc.createElement("triset");
  
  {
    QDomElement de0 = doc.createElement("name");
    QDomText tn0 = doc.createTextNode(m_fileName);
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("opacity");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_opacity));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("color");
    QDomText tn0 = doc.createTextNode(QString("%1 %2 %3").\
				      arg(m_color.x).arg(m_color.y).arg(m_color.z));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("ambient");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_ambient));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("diffuse");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_diffuse));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("specular");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_specular));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }

  {
    QDomElement de0 = doc.createElement("pointmode");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_pointMode));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }

  {
    QDomElement de0 = doc.createElement("pointsize");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_pointSize));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  {
    QDomElement de0 = doc.createElement("pointstep");
    QDomText tn0 = doc.createTextNode(QString("%1").arg(m_pointStep));
    de0.appendChild(tn0);
    de.appendChild(de0);
  }
  
  
  return de;
}

bool
TrisetObject::fromDomElement(QDomElement de)
{
  clear();

  bool ok = false;

  QString name;
  QDomNodeList dlist = de.childNodes();
  for(int i=0; i<dlist.count(); i++)
    {
      QDomElement dnode = dlist.at(i).toElement();
      QString str = dnode.toElement().text();
      if (dnode.tagName() == "name")
	ok = load(str);
      else if (dnode.tagName() == "opacity")
	m_opacity = str.toFloat();
      else if (dnode.tagName() == "color")
	{
	  QStringList xyz = str.split(" ");
	  float x = 0;
	  float y = 0;
	  float z = 0;
	  if (xyz.size() > 0) x = xyz[0].toFloat();
	  if (xyz.size() > 1) y  = xyz[1].toFloat();
	  if (xyz.size() > 2) z  = xyz[2].toFloat();
	  m_color = Vec(x,y,z);
	}
      else if (dnode.tagName() == "ambient")
	m_ambient = str.toFloat();
      else if (dnode.tagName() == "diffuse")
	m_diffuse = str.toFloat();
      else if (dnode.tagName() == "specular")
	m_specular = str.toFloat();
      else if (dnode.tagName() == "pointmode")
	{
	  if (str == "yes" || str == "1") m_pointMode = true;
	  else m_pointMode = false;
	}
      else if (dnode.tagName() == "pointsize")
	m_pointSize = str.toFloat();
      else if (dnode.tagName() == "pointstep")
	m_pointStep = str.toFloat();
    }

  return ok;
}

TrisetInformation
TrisetObject::get()
{
  TrisetInformation ti;
  ti.filename = m_fileName;
  ti.pointMode = m_pointMode;
  ti.pointStep = m_pointStep;
  ti.pointSize = m_pointSize;
  ti.color = m_color;
  ti.opacity = m_opacity;
  ti.ambient = m_ambient;
  ti.diffuse = m_diffuse;
  ti.specular = m_specular;

  return ti;
}

bool
TrisetObject::set(TrisetInformation ti)
{
  bool ok = false;

  if (m_fileName != ti.filename)
    ok = load(ti.filename);
  else
    ok = true;

  m_opacity = ti.opacity;
  m_color = ti.color;
  m_ambient = ti.ambient;
  m_diffuse = ti.diffuse;
  m_specular = ti.specular;
  m_pointMode = ti.pointMode;
  m_pointSize = ti.pointSize;
  m_pointStep = ti.pointStep;

  return ok;
}
